using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.IO;
using Mono.Unix.Native;

namespace Mehat.San.SanFS
{

	public class DirEntFactory
	{
		private static IDirectory							mRoot;
		private static ulong								mNextInode = 2;
		private static Dictionary<ulong, IDirectoryEntry>	mInodeDict;

		public static void Initialize()
		{
			mInodeDict = new Dictionary<ulong, IDirectoryEntry>();

			mRoot = _CreateDirectory(null, null, "__ROOT__", 1);
		}

		public static IFile CreateFile(IVirtualMount VMount, IDirectory Parent, string FilePath)
		{
			Stat	statBuffer;
			Guid	uuid = Guid.Empty;
			
			if (Syscall.stat(FilePath, out statBuffer) < 0)
				throw new Exception("stat failed");
				
			IFile	f = new GenericFile(GetNextInode(), FilePath, Parent, statBuffer, VMount, uuid);
			
			Parent.Lock.WaitOne();
			try
			{
				Parent.Children.Add(f.Name, (IDirectoryEntry) f);
			}
			catch
			{
				throw;
			}
			finally
			{
				Parent.Lock.ReleaseMutex();
			}

			mInodeDict.Add(f.Stat.st_ino, (IDirectoryEntry) f);
			
			return f;
		}
		
		public static IDirectory CreateDirectory(IVirtualMount VirtualMount, IDirectory Parent, string Name)
		{
			if (Name[0] == '/')
				Name = Name.Substring(1);
			if (Name[Name.Length-1] == '/')
				Name = Name.Substring(0, Name.Length-1);

			if (Name.Contains("/"))
				throw new ArgumentException("Invalid directory name", "Name");
			return _CreateDirectory(VirtualMount, Parent, Name, GetNextInode());
		}

		public static IDirectoryEntry Lookup(ulong INode)
		{
			return mInodeDict[INode];
		}

		public static IDirectoryEntry Lookup(string Path)
		{
			if (Path == "/")
				return (IDirectoryEntry) mRoot;
			IDirectoryEntry	ent = _Lookup(Path.Substring(1), mRoot);

			if (ent == null)
				throw new KeyNotFoundException();
			return ent;
		}

		public static IDirectoryEntry Lookup(IDirectory Parent, string Name, bool ThrowOnError)
		{
			Parent.Lock.WaitOne();
			try
			{
				if (!ThrowOnError && !Parent.Children.ContainsKey(Name))
					return null;
				return Parent.Children[Name];
			}
			catch
			{
				throw;
			}
			finally
			{
				Parent.Lock.ReleaseMutex();
			}
		}

		public static IDirectory CreateDirectories(IVirtualMount VirtualMount, string Path, bool Merge)
		{
			IDirectory	dirHandle = null;
			IDirectory	parentDir = mRoot;
			int			startIdx = 0;
			bool		lastComponent = false;

			if (Path.EndsWith("/"))
				Path = Path.Substring(0, Path.Length-1);

			while (!lastComponent)
			{
				string	pathComponent;
				int		endIdx = Path.IndexOf("/", startIdx);

				if (endIdx == -1)
				{
					lastComponent = true;
					pathComponent = Path;
				}
				else
					pathComponent = Path.Substring(0, endIdx + 1);

				dirHandle = (IDirectory) _Lookup(pathComponent, (IDirectory) mRoot);
				if (dirHandle == null)
				{
					string	dirName = pathComponent;

					if (dirName.EndsWith("/"))
						dirName = dirName.Substring(0, dirName.Length -1);

					if (dirName.LastIndexOf('/') != -1)
						dirName = dirName.Substring(dirName.LastIndexOf('/') + 1);
					dirHandle = CreateDirectory(VirtualMount, parentDir, dirName);
				}
				else
				{
					if (!Merge && dirHandle != mRoot)
						throw new ArgumentException(String.Format("Path component {0} already exists and Merge is false!", pathComponent), "Path");
					dirHandle.VirtualMounts.Add(VirtualMount);
				}
					
				parentDir = dirHandle;
				startIdx = endIdx + 1;
			}

			return dirHandle;
		}

		public static IDirectoryEntry CreateFile(IDirectory ParentDir)
		{
			throw new Exception("not implimented");
		}
	
		public static void DestroyEntry(IDirectoryEntry Entry)
		{
			throw new NotImplementedException();
		}

		/*
		 * Private Methods
		 */
		private static ulong GetNextInode()
		{
			return mNextInode++;	// XXX: Needs to be atomic
		}

		private static IDirectoryEntry _Lookup(string PathComponent, IDirectory DirElement)
		{
			string	nextComponent;
			int		componentLength;
			int		compIdx;

			if (PathComponent == "/")
				return (IDirectoryEntry) mRoot;

			if (PathComponent.StartsWith("/"))
				PathComponent = PathComponent.Substring(1);
			if (PathComponent.EndsWith("/"))
				PathComponent = PathComponent.Substring(0, PathComponent.Length -1);

			if ((compIdx = PathComponent.IndexOf('/')) == -1)
			{
				nextComponent = null;
				componentLength = PathComponent.Length;
			}
			else
			{
				nextComponent = PathComponent.Substring(compIdx);
				componentLength = compIdx;
			}

			DirElement.Lock.WaitOne();
			try
			{
				string	chkComponent = PathComponent.Substring(0, componentLength);

				if (!DirElement.Children.ContainsKey(chkComponent))
					return null;

				/*
				 * Component name match
				 */
				if (nextComponent == null)
					return DirElement.Children[chkComponent];
					
				return _Lookup(nextComponent, (IDirectory) DirElement.Children[chkComponent]);
			}
			catch
			{
				throw;
			}
			finally
			{
				DirElement.Lock.ReleaseMutex();
			}
		}

		private static IDirectory _CreateDirectory(IVirtualMount VirtualMount, IDirectory Parent, string Name, ulong Inode)
		{
			IDirectory	newEntry = null;
			IDirectory	tmp;
			Stat		stat = new Stat();
			ulong		dotdotInode;

			stat.st_mode =	FilePermissions.S_IFDIR | FilePermissions.S_IRUSR | FilePermissions.S_IRGRP |
							FilePermissions.S_IXGRP | FilePermissions.S_IROTH | FilePermissions.S_IXOTH;
			stat.st_size = 4096;
			stat.st_blksize = 1024;
			stat.st_blocks = 1;
			stat.st_nlink = 2;
			stat.st_atime = 0;	// XXX: fix

			newEntry = new Directory(Inode, Name, Parent, stat);
			newEntry.VirtualMounts.Add(VirtualMount);
			tmp = new Directory(newEntry.Stat.st_ino, ".", newEntry, stat);
			newEntry.Children.Add(tmp.Name, (IDirectoryEntry) tmp);
			tmp.VirtualMounts.Add(VirtualMount);

			dotdotInode = (Parent == null) ? newEntry.Stat.st_ino : Parent.Stat.st_ino;
			tmp = new Directory(dotdotInode, "..", newEntry, stat);
			newEntry.Children.Add(tmp.Name, (IDirectoryEntry) tmp);
			tmp.VirtualMounts.Add(VirtualMount);
			
			if (Parent != null)
			{
				Parent.Lock.WaitOne();
				try
				{
					Parent.Children.Add(newEntry.Name, (IDirectoryEntry) newEntry);
				}
				catch
				{
				}
				finally
				{
					Parent.Lock.ReleaseMutex();
				}
			}

			mInodeDict.Add(newEntry.Stat.st_ino, (IDirectoryEntry) newEntry);
			return newEntry;
		}

		/*
		 * Properties
		 */
		public static IDirectoryEntry Root
		{
			get { return (IDirectoryEntry) mRoot; }
		}
	
	}	// END CLASS

}	// END NAMESPACE

